package com.lime49.lockcrypt;
import java.util.*;
import java.security.MessageDigest;
import javax.crypto.spec.SecretKeySpec;
import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.plaf.FontUIResource;
import java.awt.GridBagConstraints;
import java.lang.reflect.Method;
import java.net.URL;

public class Utils {
    
    /**
     * Converts an array to a String, seperated by separators
     * @param ary The array to implode
     * @param delim The separator to use
     * @return The array as a string
     */
    public static String implode(String[] ary, String delim) {
        StringBuilder out = new StringBuilder();
        for(int i=0; i<ary.length; i++) {
            if(i!=0) { out.append(delim); }
            out.append(ary[i]);
        }
        return out.toString();
    }
    
    /**
     * Converts an array to a String, seperated by separators
     * @param ary The LinkedHashMap to implode
     * @param fields The fields to use to order the string
     * @param delim The separator to use
     * @return The LinkedHashMap as a string
     */
    public static String implode(LinkedHashMap ary, String[] fields, String delim) {
        StringBuilder out = new StringBuilder();
        int i = 0;
        for(int j=0;j<fields.length;j++) {
            for(Object key : ary.keySet()) {
              if(fields[j].equals(key)) {
                if(i!=0) { 
                    out.append(delim); 
                } else {
                    i++;
                }
                out.append(ary.get(key));
              }
            }
        }
        return out.toString();
    }
    
    /**
     * Searches haystack for needle, returning true if found, otherwise false
     * @param haystack The array to search
     * @param needle The string to find
     * @return
     */
    public static boolean in_array(String[] haystack, String needle) {
        for(int i=0;i<haystack.length;i++) {
            if(haystack[i].equals(needle)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Generates an AES SecretKeySpec for use with encryption by first creating an MD5 hash of the key
     * @param keystring The string to use
     * @return The generated AES SecretKeySpec
     * @throws java.lang.Exception
     */
    public static SecretKeySpec getKeySpec(String keystring) throws Exception {
        byte[] key = new byte[16];
        MessageDigest digest = MessageDigest.getInstance("MD5");
        digest.update(keystring.getBytes("UTF-8"));
        byte[] hash = digest.digest();
        int len = hash.length;
        System.arraycopy(hash, 0, key, 0, len);
        return new SecretKeySpec(hash, "AES");
    }

    public static ResourceBundle getLanguage(String langCode) {
        ResourceBundle lang = null;
        if(langCode.contains("_")) {
            String country = langCode.substring(0,2);
            if(country.equals("zh")) {
                if(langCode.endsWith("t")) { // chinese tradtional
                    lang = ResourceBundle.getBundle("com.lime49.lockcrypt.lang.lang", new Locale(country, "TW"));
                } else if(langCode.endsWith("s")) { // chinese simplified
                    lang = ResourceBundle.getBundle("com.lime49.lockcrypt.lang.lang", new Locale(country, "CN"));
                } else {
                    lang = ResourceBundle.getBundle("com.lime49.lockcrypt.lang.lang", new Locale(country, langCode.substring(3)));
                }
            } else if(country.equals("he")) {
                lang = ResourceBundle.getBundle("com.lime49.lockcrypt.lang.lang", new Locale("iw", langCode.substring(3))); // bug as of Java v1.6.7
            } else {
                lang = ResourceBundle.getBundle("com.lime49.lockcrypt.lang.lang", new Locale(country, langCode.substring(3)));
            }
        } else if(langCode.equals("ka")) { //georgian
            lang = ResourceBundle.getBundle("com.lime49.lockcrypt.lang.lang", new Locale(langCode));
            Utils.getSafeFont(langCode, true);
        } else {
            lang = ResourceBundle.getBundle("com.lime49.lockcrypt.lang.lang", new Locale(langCode));
        }
        return lang;
    }

    /**
     * Returns a font suitable for rendering the UI language. If the current font is capable, uses that, otherwise tries Arial Unicode MS.
     * Also sets the UI default to the safe font found.
     * @param lang The launguage code of the language needed
     * @param updateUI If true, will also update the UI default to a suitable font
     * @return A font capable of displaying the language.
     */
    public static Font getSafeFont(String lang, boolean updateUI) {
        Font font = null;
        boolean useDefaultFont = false;
        if(lang.startsWith("zh") || lang.startsWith("iw")) { // chinese, hebrew
            font = new FontUIResource("Arial Unicode MS", Font.PLAIN, 12);
            if(font == null) { // mac OS
                font = new FontUIResource("Lucida Unicode", Font.PLAIN, 12);
            }
        } else if(lang.startsWith("ka")) { // georgian
            font = new FontUIResource("Sylfaen", Font.PLAIN, 12);
            if(font == null) {
                font = new FontUIResource("Arial Unicode MS", Font.PLAIN, 12);
                if(font == null) {
                    font = new FontUIResource("Lucida Unicode", Font.PLAIN, 12);
                }
            }
        } else {
            useDefaultFont = true;
        }
        if(font == null) { // last resort, try arial
            font = new FontUIResource("Arial", Font.PLAIN, 12);
        }
        if(updateUI) {
            Enumeration keys = UIManager.getDefaults().keys();
            if(useDefaultFont) {
                while(keys.hasMoreElements()) {
                    Object key = keys.nextElement();
                    Object value = UIManager.get(key);
                    if(value instanceof FontUIResource) {
                        UIManager.put (key, value);
                    }
                }
            } else {
                while(keys.hasMoreElements()) {
                    Object key = keys.nextElement();
                    Object value = UIManager.get(key);
                    if(value instanceof FontUIResource) {
                        UIManager.put (key, font);
                    }
                }
            }
        }
        return font;
    }
    
    /**
     * Opens the default browser to the specified URL. Should work on Windows, Linux, Unix and MacOS
     * @param url The URL to open
     */
    public static void openURL(String url) {
        // MultiOS browser launch, modified from http://centerkey.com/java/browser/ . Originally by Dem Pilafian
        String osName = System.getProperty("os.name");
        try {
            if(osName.startsWith("Mac OS")) {
                //TODO: Fix MacOS support, generated warnings on compile
                Class<?> fileMgr = Class.forName("com.apple.eio.FileManager");
                Method openURL = fileMgr.getDeclaredMethod("openURL",new Class[] {String.class});
                openURL.invoke(null, new Object[] {url});
            } else if (osName.startsWith("Windows")) {
                Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
            } else { //assume Unix or Linux
                String[] browsers = { "firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape" };
                String browser = null;
                for(int count = 0; count < browsers.length && browser == null; count++) {
                    if(Runtime.getRuntime().exec(new String[] {"which", browsers[count]}).waitFor() == 0) {
                        browser = browsers[count];
                    }
                }
                if(browser == null) {
                    throw new Exception("Could not find web browser");
                } else {
                    Runtime.getRuntime().exec(new String[] {browser, url});
                }
            }
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, "There was a problem launching your browser:" + ":\n" + e.getLocalizedMessage(), "Error", JOptionPane.ERROR_MESSAGE);
        }
    }
   
    /**
     * Sets the parameters of the specified GridBagConstraints
     * @param c The constraints to set
     * @param row Row number
     * @param col Column number
     * @param width Grid width
     * @param height Grid height
     * @param fill NONE, HORIZONTAL, VERTICAL OR BOTH
     * @param anchor One of either FIRST_LINE_START, PAGE_START, FIRST_LINE_END, LINE_START, CENTER, LINE_END, LAST_LINE_START, PAGE_END,LAST_LINE_END
     * @param weightx
     */
    public static void setgrid(GridBagConstraints c, int row, int col, int width, int height, int fill, int anchor, double weightx) {
        c.gridx = col;
        c.gridy = row;
        c.gridwidth = width;
        c.gridheight = height;
        c.fill = fill;
        c.anchor = anchor;
        c.weightx = weightx;
    }
   
    /**
     * Convert all applicable characters to HTML entities
     * @param text The input text
     * @param useUnderscores If true, replaces spaces with underscores
     * @return The encoded string
     */
    public static String HTMLEntities(String text, boolean useUnderscores) {
        StringBuffer buf = new StringBuffer();
        int len = (text == null ? -1 : text.length());
        for(int i = 0;i < len;i++) {
            char c = text.charAt(i);
            if((c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9')) {
                buf.append(c);
            } else if(c == ' ') {
                if(useUnderscores) {
                    buf.append('_');
                } else {
                    buf.append(' ');
                }
            } else {
                buf.append("&#"+(int)c+";");
            }
        }
        return buf.toString();
    }
   
    /**
     * Removes all spaces and non alpha-numeric characters and replaces them with underscores
     * @param text The input text
     * @return The converted string
     */
    public static String toUnderscores(String text) {
        StringBuffer buf = new StringBuffer();
        int len = (text == null ? -1 : text.length());
        for(int i = 0;i < len;i++) {
            char c = text.charAt(i);
            if((c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9')) {
                buf.append(c);
            } else {
                buf.append('_');
            }
        }
        return buf.toString();
    }
   
    /**
     * Finds the index in the specified ArrayList where the specified name exists
     */
    public static int getArrayListIndex(String name, ArrayList<? extends AccountTreeNode> arrayList) {
        int numaccounts = arrayList.size();
         for(int i=0;i<numaccounts;i++) {
             if(arrayList.get(i).getName().equals(name)) {
                 return i;
             }
         }
         return -1;
    }
   
    /**
     * Finds the index in the specified tree where the specified name exists
     */
    public static int getTreeIndex(String name, AccountTree tree) {
        for(int i=0;i<tree.getRowCount();i++) {
            if(tree.getPathForRow(i).getLastPathComponent().toString().equals(name)) {
                return i;
            }
        }
        return -1;
    }
  
    /**
     * Finds the index in the account ArrayList where the specified account exists
     * @param typename The type to search for
     * @param location "Tree" or "visible" to search only the type list in the options window, anything else to search the whole list of types (including deleted ones)
     * @return
     */
    public static int getTypeIndex(String typename, ArrayList<AccType> types, String location) {
        // returns the index in 'types'/'typeListModel' where 'typename' is stored
        if(location.equals("list")) {
            int numtypes = types.size();
            for(int i=0;i<numtypes;i++) {
                if(types.get(i).getName().equals(typename) && !types.get(i).isDeleted) {
                    return i;
                }
            }
        } else {
            int numtypes = types.size();
            for(int i=0;i<numtypes;i++) {
                if(types.get(i).getName().equals(typename)) {
                    return i;
                }
            }
        }
        return -1;
    }
   
    public static class IconRenderer extends DefaultTableCellRenderer implements TableCellRenderer {
        @Override
        public Component getTableCellRendererComponent(
                        JTable table, Object img,
                        boolean isSelected, boolean hasFocus,
                        int row, int column) {
            if(img == null || img.equals("")) {
                return null;
            } else {
                try {
                    this.setHorizontalAlignment(SwingConstants.CENTER);
                    this.setVerticalAlignment(SwingConstants.CENTER);
                    URL iconURL = this.getClass().getResource("images/types/"+img+"_32.png");
                    ImageIcon imgIcon = new ImageIcon(Toolkit.getDefaultToolkit().getImage(iconURL));
                    this.setIcon(imgIcon);
                    this.setText("");
                    if(isSelected) {
                        this.setBackground(Color.getHSBColor(0.58f, 0.2f, 1f));
                    } else {
                        this.setBackground(Color.WHITE);
                    }
                } catch(Exception ignore) {}
            }
            return this;
        }
    }
    
    /**
     * Finds the next available name by appending numbers, discounting items which already exist
     * @param prefix The prefix to use
     * @param location The location to search for items
     * @return The next available item name
     */
    public static String getNextAvailableAccountName(String prefix, ArrayList<? extends AccountTreeNode> location) {
        if(Utils.getArrayListIndex(prefix, location) < 0) {
            return prefix;
        } else {
            boolean gotName = false;
            int k = 1;
            String name = prefix;
            while(!gotName) {
                if(Utils.getArrayListIndex(prefix+" "+k, location) >= 0) {
                    k++;
                } else {
                    name = prefix +" "+ k;
                    gotName = true;
                }
            }
            return name;
        }
    }
    
    /**
     * Generates a (hopefully) unique ID using a combination of 53 (non-HTML safe) or 39 (HTML safe) characters.
     * There are 1.74e17 combinations if the ID does not need to be HTML safe, otherwise there are just over 3500 million
     * @param HTMLSafe true if the generated ID must be valid in HTML without encoding.</param
     */
    public static String GenID(boolean HTMLSafe) {
        StringBuffer bld = new StringBuffer();
        Random seed = new Random();
        int len = 6;
        if(HTMLSafe) {
            String possible = "ABCEDFGHIJKLMNOPQRSTUVWXYZ0123456789~_-",
                   firstPossible = "ABCEDFGHIJKLMNOPQRSTUVWXYZ"; // make sure the first character isn't a number, which invalidates xhtml
            bld.append(possible.charAt(seed.nextInt(firstPossible.length() - 1)));
            for(int i = 1; i < len; i++) {
                bld.append(possible.charAt(seed.nextInt(possible.length() - 1)));
            }
        } else {
            String possible = "ABCEDFGHIJKLMNOPQRSTUVWXYZ0123456789%£$!*/#~][{}|=_-^";
            for(int i = 0; i < len; i++) {
                bld.append(possible.charAt(seed.nextInt(possible.length() - 1)));
            }
        }
        return bld.toString();
    }
    
    /**
     * Formats a URL to remove unneeded query strings/protocol information
     * @param fullURL The URL to strip
     * @param makePretty If true, remove the path and protocol, leaving the domain
     * @return The formatted URL
     */
    public static String stripURL(String fullURL, boolean makePretty) {
        StringBuffer ret = new StringBuffer();
        try {
            URL url = new URL(fullURL);
            String protocol = url.getProtocol().toLowerCase();
            String host = url.getHost();
            if(makePretty) {
                if(protocol.equals("file")) {
                    String fileName = url.getFile();
                    int questionMark = fileName.indexOf("?");
                    if(questionMark >= 0) { // query string exists
                        if(questionMark < fileName.length()-1) { // if there are chars after the ?
                            fileName = fileName.substring(0,questionMark);
                        }
                    }
                    int lastSlash = fileName.lastIndexOf("/")+1;
                    if(lastSlash == fileName.length()) {
                        ret.append(fileName);
                    } else {
                        ret.append(fileName.substring(lastSlash));
                    }
                } else {
                    if(host.startsWith("www.")) {
                        ret.append(host.substring(4,5).toUpperCase())
                           .append(host.substring(5));
                    } else {
                        ret.append(host.substring(0,1).toUpperCase())
                           .append(host.substring(1));
                    }
                }
            } else {
                ret.append(protocol).append("://");
                if(protocol.equals("file")) {
                    ret.append(url.getPath());
                } else {
                    ret.append(url.getHost());
                }
            }
        } catch(Exception ex) { ret.append(fullURL); }
        
        return ret.toString();
    }
    
    public static String getJARPath() {
        String install_path = LockCrypt.class.getProtectionDomain().getCodeSource().getLocation().toString();
        install_path = install_path.replace("\\file:\\", "").
                                    replace("file://", "").
                                    replace("file:/", "");
        if(install_path.indexOf("LockCrypt.jar") >= 0) 
            install_path = install_path.substring(0, install_path.lastIndexOf("LockCrypt.jar"));
        return install_path;
    }

    public static boolean urlMatches(URL url, URL urlToMatch) {
        boolean match = false;
        if(url.getProtocol().equals("*")) {
            String accountHost = url.getHost();
            if(accountHost.length() > 0) {
                if(accountHost.charAt(0) == '*') {
                    if(accountHost.charAt(accountHost.length()) == '*') { // *host*
                        if(accountHost.length() > 2 && urlToMatch.getHost().contains(accountHost.substring(1, accountHost.length()-3).toLowerCase())) {
                            match = true;
                        }
                    } else { // *host
                        if(accountHost.length() > 1 && urlToMatch.getHost().endsWith(accountHost.substring(1).toLowerCase())) {
                            match = true;
                        }
                    }
                } else if(accountHost.charAt(accountHost.length()) == '*') { // host*
                    if(urlToMatch.getHost().startsWith(accountHost.substring(0, accountHost.length()-2).toLowerCase())) {
                        match = true;
                    }
                } else if(accountHost.equalsIgnoreCase(urlToMatch.getHost())) { // host
                    match = true;
                }
            }
        } else if(url.getProtocol().equalsIgnoreCase(urlToMatch.getProtocol())) {
            match = true;
        }
        return match;
    }
}
